package template

import (
	"io/ioutil"
	"os"
	"strings"
)

type gRPCKind struct{}

func (g *gRPCKind) Gen(rootDir string) error {
	var err error
	rootDir += "/" + *projectRepo
	err = os.MkdirAll(rootDir, 0775)
	if err != nil {
		return err
	}
	common := new(commonKind)
	err = common.Gen(rootDir)
	if err != nil {
		return err
	}
	// go mod
	{
		modData := projectReplacer.Replace(templateGRPCGoMod)
		modData = versionReplacer.Replace(modData)
		err := ioutil.WriteFile(rootDir+"/go.mod", []byte(modData), 0666)
		if err != nil {
			return err
		}
	}
	// etc
	{
		etcDir := rootDir + "/etc"
		err := os.MkdirAll(etcDir, 0775)
		if err != nil {
			return err
		}
		etcAppIni := etcDir + "/app.ini.example"
		dataIni := projectReplacer.Replace(templateEtcGRPCAppIniExample)
		err = ioutil.WriteFile(etcAppIni, []byte(dataIni), 0666)
		if err != nil {
			return err
		}
		etcAppDockerIni := etcDir + "/app-docker.ini.example"
		dataDockerIni := projectReplacer.Replace(templateEtcGRPCAppDockerIniExample)
		err = ioutil.WriteFile(etcAppDockerIni, []byte(dataDockerIni), 0666)
		if err != nil {
			return err
		}
	}
	// other
	{
		protoDir := rootDir + "/proto"
		err := os.MkdirAll(protoDir, 0775)
		if err != nil {
			return err
		}
		httpServerDir := rootDir + "/http_server"
		err = os.MkdirAll(httpServerDir, 0775)
		if err != nil {
			return err
		}
		swaggerData := projectReplacer.Replace(templateGRPCHttpServerSwagger)
		err = ioutil.WriteFile(httpServerDir+"/swagger.go", []byte(swaggerData), 0666)
		if err != nil {
			return err
		}
	}
	// server
	{
		serverDir := rootDir + "/server"
		err = os.MkdirAll(serverDir, 0775)
		if err != nil {
			return err
		}
		serverData := projectReplacer.Replace(templateGRPCServerS)
		serverData = projectProtoReplacer.Replace(serverData)
		err = ioutil.WriteFile(serverDir+"/"+*projectRepo+".go", []byte(serverData), 0666)
		if err != nil {
			return err
		}
	}
	// service
	{
		serviceDir := rootDir + "/service"
		err = os.MkdirAll(serviceDir, 0775)
		if err != nil {
			return err
		}

		serviceData := projectReplacer.Replace(templateGRPCServiceS)
		err = ioutil.WriteFile(serviceDir+"/"+*projectRepo+".go", []byte(serviceData), 0666)
		if err != nil {
			return err
		}
	}
	// repository
	{
		repositoryDir := rootDir + "/repository"
		err = os.MkdirAll(repositoryDir, 0775)
		if err != nil {
			return err
		}
		err := ioutil.WriteFile(repositoryDir+"/repository.go", []byte("package repository"), 0666)
		if err != nil {
			return err
		}
	}
	// startup
	{
		startupDir := rootDir + "/startup"
		registerData := projectReplacer.Replace(templateGRPCStartupRegister)
		registerData = projectProtoReplacer.Replace(registerData)
		err = ioutil.WriteFile(startupDir+"/register.go", []byte(registerData), 0666)
		if err != nil {
			return err
		}
		configData := projectReplacer.Replace(templateGRPCStartupConfig)
		err = ioutil.WriteFile(startupDir+"/config.go", []byte(configData), 0666)
		if err != nil {
			return err
		}
	}
	// vars
	{
		varsDir := rootDir + "/vars"
		err := os.MkdirAll(varsDir, 0775)
		if err != nil {
			return err
		}
		err = ioutil.WriteFile(varsDir+"/settings.go", []byte(templateGRPCVarsSettings), 0666)
		if err != nil {
			return err
		}
		err = ioutil.WriteFile(varsDir+"/vars.go", []byte(templateGRPCVarsVars), 0666)
		if err != nil {
			return err
		}
	}
	//client
	{
		clientDir := rootDir + "/client"
		err := os.Mkdir(clientDir, 0775)
		if err != nil {
			return err
		}
		testGoPath := clientDir + "/" + strings.ReplaceAll(*projectRepo, "-", "_") + "_test.go"
		testGoData := projectReplacer.Replace(templateGRPCClientTest)
		testGoData = projectProtoReplacer.Replace(testGoData)
		err = ioutil.WriteFile(testGoPath, []byte(testGoData), 0666)
		if err != nil {
			return err
		}
	}
	// main.go
	{
		mainData := projectReplacer.Replace(templateGrpcMain)
		err := ioutil.WriteFile(rootDir+"/main.go", []byte(mainData), 0666)
		if err != nil {
			return err
		}
	}
	return nil
}

func (g *gRPCKind) Kind() string {
	return "grpc"
}

const (
	templateGRPCClientTest = `package client

import (
	"context"
	"{namespace}/{service_name}/model/args"
	"{namespace}/{service_name}/proto/{service_name_proto}_proto/{service_name_proto}"
	"{namespace}/{service_name}/util"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"testing"
	"time"
)

func TestService(t *testing.T) {
	// 在这里写功能测试用例，压力测试用例
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	conn, err := util.GetGrpcClient(ctx, args.RpcServiceXXX)
	if err != nil {
		t.Errorf("Conn err: %v", err)
		return
	}
	// defer conn.Close() // 应该复用建立好的连接

	client := {service_name_proto}.NewYourServiceClient(conn)
	r := {service_name_proto}.Service1Request{
		Id: 1024,
	}
	{
		var header, trailer metadata.MD
		result, err := client.Service1(ctx, &r, grpc.Header(&header), grpc.Trailer(&trailer))
		if err != nil {
			t.Error("Service1 err ", err)
		} else {
			t.Log("resp header =>")
			for i, v := range header {
				t.Logf("%v=%v", i, v)
			}
			t.Log("resp trailer =>")
			for i, v := range trailer {
				t.Logf("%v=%v", i, v)
			}
			t.Log("")
			t.Logf("Service1 result: %+v", result)
		}
	}
}

`
	templateGRPCGoMod = `module {namespace}/{service_name}

go 1.13

require (
	gitee.com/kelvins-io/common {version_common}
	gitee.com/kelvins-io/kelvins {version_kelvins}
	github.com/golang/protobuf v1.5.2
	github.com/gomodule/redigo v2.0.0+incompatible
	github.com/google/uuid v1.2.0
	github.com/grpc-ecosystem/grpc-gateway v1.16.0
	google.golang.org/grpc v1.40.0
	google.golang.org/protobuf v1.26.0
)`
	templateGRPCStartupConfig = `package startup

import (
	"{namespace}/{service_name}/vars"
	"gitee.com/kelvins-io/kelvins/config"
)

const (
	SectionEmailConfig = "email-config"
	SectionEmailNotice = "email-notice"
)

// LoadConfig 加载配置对象映射
func LoadConfig() error {
	// 举例说明
	vars.EmailConfigSetting = new(vars.EmailConfigSettingS)
	config.MapConfig(SectionEmailConfig, vars.EmailConfigSetting)
	
	vars.EmailNoticeSetting = new(vars.EmailNoticeSettingS)
	config.MapConfig(SectionEmailNotice, vars.EmailNoticeSetting)
	return nil
}

`
	templateGRPCVarsVars = `package vars

var (
	EmailNoticeSetting *EmailNoticeSettingS
	EmailConfigSetting *EmailConfigSettingS
)
`
	templateGRPCVarsSettings = `package vars

type EmailConfigSettingS struct {
	User     string
	Password string
	Host     string
	Port     string
}

type EmailNoticeSettingS struct {
	Receivers []string
}`
	templateGrpcMain = `package main

import (
	"{namespace}/{service_name}/startup"
	"gitee.com/kelvins-io/kelvins"
	"gitee.com/kelvins-io/kelvins/app"
)

const APP_NAME = "{service_name}"

func main() {
	application := &kelvins.GRPCApplication{
		Application: &kelvins.Application{
			LoadConfig: startup.LoadConfig,
			SetupVars:  startup.SetupVars,
			StopFunc:	startup.StopFunc,
			Name:       APP_NAME,
		},
		RegisterGRPCHealthHandle: startup.RegisterGRPCHealthHandle,
		RegisterGRPCServer:   startup.RegisterGRPCServer,
		RegisterGateway:      startup.RegisterGateway,
		RegisterHttpRoute:    startup.RegisterHttpRoute,
	}
	app.RunGRPCApplication(application)
}`
	templateGRPCHttpServerSwagger = `package http_server

import (
	"{namespace}/{service_name}/proto"
	"log"
	"net/http"
	"strings"
)

// SwaggerHandler 处理swagger.json文件的http请求
func SwaggerHandler(w http.ResponseWriter, r *http.Request) {
	if !strings.HasSuffix(r.URL.Path, "swagger.json") {
		log.Printf("Not Found: %s", r.URL.Path)
		http.NotFound(w, r)
		return
	}

	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type,api_key,Authorization")
	w.Header().Set("content-type", "application/json")
	path := strings.TrimPrefix(r.URL.Path, "/swagger/")
	bytes, err := proto.Asset(path) // pb_gateway工具生成proto对应的swagger_json.go
	if err == nil {
		w.Write(bytes)
	}
}`
	templateGRPCServerS = `package server

import (
	"context"
	"fmt"
	"{namespace}/{service_name}/proto/{service_name_proto}_proto/{service_name_proto}"
	"{namespace}/{service_name}/service"
)

type YourServer struct{}

func NewYourServer() {service_name_proto}.YourServiceServer {
	return new(YourServer)
}

func (s *YourServer) Service1(ctx context.Context, req *{service_name_proto}.Service1Request) (*{service_name_proto}.Service1Response, error) {
	return &{service_name_proto}.Service1Response{
		Common: &{service_name_proto}.CommonResponse{
			Code: {service_name_proto}.RetCode_SUCCESS,
			Msg:  fmt.Sprintf("%d", req.Id),
		},
		Result: service.Service1(ctx),
	}, nil
}

func (s *YourServer) Service2(ctx context.Context, req *{service_name_proto}.Service2Request) (*{service_name_proto}.Service2Response, error) {
	return &{service_name_proto}.Service2Response{
		Common: &{service_name_proto}.CommonResponse{
			Code: {service_name_proto}.RetCode_SUCCESS,
			Msg:  fmt.Sprintf("%d", req.Id),
		},
		Result: service.Service2(ctx),
	}, nil
}

func (s *YourServer) Service3(ctx context.Context, req *{service_name_proto}.Service3Request) (*{service_name_proto}.Service3Response, error) {
	return &{service_name_proto}.Service3Response{
		Common: &{service_name_proto}.CommonResponse{
			Code: {service_name_proto}.RetCode_SUCCESS,
			Msg:  fmt.Sprintf("%d", req.Id),
		},
		Result: service.Service3(ctx),
	}, nil
}
`
	templateGRPCServiceS = `package service

import (
	"context"
	"gitee.com/kelvins-io/kelvins"
	"gitee.com/kelvins-io/kelvins/util/queue_helper"
	"time"
)

func Service1(ctx context.Context) string {
	// some services handle
	go func() {
		pushHelper,err := queue_helper.NewPublishService(kelvins.QueueServerAMQP, &queue_helper.PushMsgTag{
			DeliveryTag:    "{service_name}_notice",
			DeliveryErrTag: "{service_name}_notice_err",
			RetryCount:     2,
			RetryTimeout:   3600,
		}, kelvins.BusinessLogger)
		if err == nil {
			type notice struct {
				Service string
				Time int64
			}
			_, _ = pushHelper.PushMessage(context.TODO(),&notice{
				Service: "service1",
				Time:    time.Now().Unix(),
			})
		}
	}()
	return "service1"
}

func Service2(ctx context.Context) string {
	// some services handle
	go func() {
		pushHelper,err := queue_helper.NewPublishService(kelvins.QueueServerAMQP, &queue_helper.PushMsgTag{
			DeliveryTag:    "{service_name}_notice",
			DeliveryErrTag: "{service_name}_notice_err",
			RetryCount:     2,
			RetryTimeout:   3600,
		}, kelvins.BusinessLogger)
		if err == nil {
			type notice struct {
				Service string
				Time int64
			}
			_, _ = pushHelper.PushMessage(context.TODO(), &notice{
				Service: "service2",
				Time:    time.Now().Unix(),
			})
		}
	}()
	return "service2"
}

func Service3(ctx context.Context) string {
	// some services handle
	go func() {
		pushHelper,err := queue_helper.NewPublishService(kelvins.QueueServerAMQP, &queue_helper.PushMsgTag{
			DeliveryTag:    "{service_name}_notice",
			DeliveryErrTag: "{service_name}_notice_err",
			RetryCount:     2,
			RetryTimeout:   3600,
		}, kelvins.BusinessLogger)
		if err == nil {
			type notice struct {
				Service string
				Time int64
			}
			_, _ = pushHelper.PushMessage(context.TODO(),&notice{
				Service: "service3",
				Time:    time.Now().Unix(),
			})
		}
	}()
	return "service3"
}

`
	templateGRPCStartupRegister = `package startup

import (
	"context"
	"gitee.com/kelvins-io/kelvins"
	healthpb "google.golang.org/grpc/health/grpc_health_v1"
	"{namespace}/{service_name}/proto/{service_name_proto}_proto/{service_name_proto}"
	"{namespace}/{service_name}/server"
	"net/http"
	"time"

	"{namespace}/{service_name}/http_server"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"google.golang.org/grpc"
)

// RegisterGRPCServer 此处注册pb的Server
func RegisterGRPCServer(grpcServer *grpc.Server) error {
	// 在这里将proto生成pb.go里面的server注册到grpcServer
	{service_name_proto}.RegisterYourServiceServer(grpcServer, server.NewYourServer())
	return nil
}

// RegisterGateway 此处注册pb的Gateway
func RegisterGateway(ctx context.Context, gateway *runtime.ServeMux, endPoint string, dopts []grpc.DialOption) error {
	// 在这里将proto生成的pb.gw.go里面的ServiceHandlerFromEndpoint注册进gateway *runtime.ServeMux
	if err := {service_name_proto}.RegisterYourServiceHandlerFromEndpoint(ctx, gateway, endPoint, dopts); err != nil {
		return err
	}
	return nil
}

// RegisterHttpRoute 此处注册http接口
func RegisterHttpRoute(serverMux *http.ServeMux) error {
	serverMux.HandleFunc("/swagger/", http_server.SwaggerHandler)
	return nil
}

const (
	healthCheckSleep = 30 * time.Second
)

func RegisterGRPCHealthHandle(health *kelvins.GRPCHealthServer) {
	// first set
	health.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
	health.SetServingStatus("{service_name_proto}.YourService", healthpb.HealthCheckResponse_SERVING)
	ticker := time.NewTicker(healthCheckSleep)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			health.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
			health.SetServingStatus("{service_name_proto}.YourService", healthpb.HealthCheckResponse_SERVING)
		case <-kelvins.AppCloseCh:
			health.SetServingStatus("", healthpb.HealthCheckResponse_NOT_SERVING)
			health.SetServingStatus("{service_name_proto}.YourService", healthpb.HealthCheckResponse_NOT_SERVING)
			return
		}
	}
}
`
	templateEtcGRPCAppIniExample = `[kelvins-server]
Environment = "dev"

[kelvins-http-server]
Network = "tcp"
ReadTimeout = 30
WriteTimeout = 30
IdleTimeout = 30

[kelvins-logger]
RootPath = "./logs"
Level = "debug"

[kelvins-rpc-server]
NumServerWorkers = 50
DisableHealthServer = false
DisableClientDialHealthCheck = false

[kelvins-rpc-rate-limit]
MaxConcurrent = 0

[kelvins-rpc-auth]
Token = "c9VW6ForlmzdeDkZE2i8"
ExpireSecond = 100
TransportSecurity = false

[kelvins-mysql]
Host = "127.0.0.1:3306"
UserName = "root"
Password = "xxx"
DBName = "{service_name}"
Charset = "utf8mb4"
PoolNum =  10
MaxIdleConns = 5
ConnMaxLifeSecond = 3600
MultiStatements = true
ParseTime = true

[kelvins-redis]
Host = "127.0.0.1:6379"
Password = "xxx"
DB = 12
PoolNum = 10

[kelvins-queue-amqp]
Broker = "amqp://micro-mall:szJ9aePR@127.0.0.1:5672/micro-mall"
DefaultQueue = "kelvins-template_notice"
ResultBackend = "redis://xxx@127.0.0.1:6379/8"
ResultsExpireIn = 3600
Exchange = "kelvins-template_notice"
ExchangeType = "direct"
BindingKey = "kelvins-template_notice"
PrefetchCount = 3
TaskRetryCount = 3
TaskRetryTimeout = 60

[email-config]
User = "xxx@qq.com"
Password = "xxxx"
Host = "smtp.qq.com"
Port = "465"

[email-notice]
Receivers = "610905744@qq.com"
`
	templateEtcGRPCAppDockerIniExample = `[kelvins-server]
Environment = "dev"

[kelvins-http-server]
Network = "tcp"
ReadTimeout = 30
WriteTimeout = 30
IdleTimeout = 30

[kelvins-logger]
RootPath = "./logs"
Level = "debug"

[kelvins-rpc-server]
NumServerWorkers = 50
DisableHealthServer = false
DisableClientDialHealthCheck = false

[kelvins-rpc-rate-limit]
MaxConcurrent = 0

[kelvins-rpc-auth]
Token = "c9VW6ForlmzdeDkZE2i8"
ExpireSecond = 100
TransportSecurity = false

[kelvins-mysql]
Host = "mysql5_7:3306"
UserName = "root"
Password = "micro-mall"
DBName = "{service_name}"
Charset = "utf8mb4"
PoolNum =  10
MaxIdleConns = 5
ConnMaxLifeSecond = 3600
MultiStatements = true
ParseTime = true

[kelvins-redis]
Host = "redis:6379"
Password = "oyuioyiuoyui"
DB = 12
PoolNum = 10

[kelvins-queue-amqp]
Broker = "amqp://micro-mall:szJ9aePR@127.0.0.1:5672/micro-mall"
DefaultQueue = "kelvins-template_notice"
ResultBackend = "redis://xxx@127.0.0.1:6379/8"
ResultsExpireIn = 3600
Exchange = "kelvins-template_notice"
ExchangeType = "direct"
BindingKey = "kelvins-template_notice"
PrefetchCount = 3
TaskRetryCount = 3
TaskRetryTimeout = 60

[email-config]
User = "xxx@qq.com"
Password = "xxxx"
Host = "smtp.qq.com"
Port = "465"

[email-notice]
Receivers = "610905744@qq.com"
`
)
